package org.xnat.xnatfs.webdav;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.xnat.xnatfs.asset.IAsset;
import org.xnat.xnatfs.asset.IStringAsset;
import org.xnat.xnatfs.exception.RequestException;
import org.xnat.xnatfs.util.JSONUtils;

import com.bradmcevoy.http.Auth;
import com.bradmcevoy.http.CollectionResource;
import com.bradmcevoy.http.GetableResource;
import com.bradmcevoy.http.Range;
import com.bradmcevoy.http.Resource;
import com.bradmcevoy.http.XmlWriter;
import com.bradmcevoy.http.exceptions.NotAuthorizedException;

abstract public class VirtualDirectory extends VirtualResource implements CollectionResource, GetableResource {
  private static final Logger logger = Logger.getLogger ( VirtualDirectory.class );
  protected static final String RESOURCES = "Resources";

  // Column names to use to look up the child key.  They are searched in order.
  List<String> childKeys = new ArrayList<String> ();
  // List of column names for the child name.  If empty, use the key.
  List<String> childNames = new ArrayList<String> ();
  String mURL;
  String elementURL;

  HashMap<String, ChildEntry> childMap = new HashMap<String, ChildEntry> ();
  boolean haveCachedChildMap = false;

  // Verbose directory and mExtraChildren are added when the hierarchy is
  // verbose.
  boolean verboseDirectory = false;
  String verboseDirectoryName = null;
  List<String> extraChildren = new ArrayList<String> ();

  public VirtualDirectory ( XNATFS x, String path, String name, String url ) {
    super ( x, path, name );
    mURL = url;
  }

  @Override
  public abstract VirtualResource child ( String arg0 );

  protected ChildEntry getChildEntryFromJSON ( JSONObject obj ) throws JSONException {
    for ( String k : childKeys ) {
      // logger.debug ( "get ChildEntryFromJSON: Checking " + k );
      if ( obj.has ( k ) ) {
        // Check for a name
        for ( String n : childNames ) {
          if ( obj.has ( n ) ) {
            // logger.debug ( "getChildEntryFromJSON: had name " + n + " == " + obj.getString ( n ) + " for ID " + obj.getString ( k ) );
            return new ChildEntry ( obj.getString ( n ), obj.getString ( k ), obj.getString ( n ) );
          }
        }
        return new ChildEntry ( obj.getString ( k ) );
      }
    }
    return null;
  }

  @Override
  public List<? extends Resource> getChildren () {
    List<Resource> list = new ArrayList<Resource> ();

    if ( xnatfs.useVerboseFolders () & !verboseDirectory & verboseDirectoryName != null ) {
      list.add ( child ( verboseDirectoryName ) );
      for ( String c : extraChildren ) {
        list.add ( child ( c ) );
      }
      return list;
    }

    try {
      getElementList ();
      for ( String child : childMap.keySet () ) {
        list.add ( child ( child ) );
      }
      Collections.sort ( list, new Comparator<Resource> () {
        public int compare ( Resource o1, Resource o2 ) {
          return o1.getName ().compareTo ( o2.getName () );
        }
      } );
    } catch ( Exception e ) {
      logger.warn ( "Failed to get child element list: " + e );
    }
    return list;
  }

  @Override
  public Long getContentLength () {
    return null;
  }

  @Override
  public String getContentType ( String accepts ) {
    return "text/html";
  }

  protected HashMap<String, ChildEntry> getElementList () throws JSONException, RequestException {
    if ( haveCachedChildMap ) {
      return childMap;
    }
    JSONObject json = JSONUtils.parseJSON ( (IStringAsset) retreiveAsset () );
    JSONArray rows = json.getJSONObject ( "ResultSet" ).getJSONArray ( "Result" );

    for ( int idx = 0; idx < rows.length (); idx++ ) {
      if ( rows.isNull ( idx ) ) {
        continue;
      }

      ChildEntry child = getChildEntryFromJSON ( rows.getJSONObject ( idx ) );

      if ( child != null ) {
        childMap.put ( child.key, child );
      }
    }
    haveCachedChildMap = true;
    return childMap;
  }

  @Override
  public Long getMaxAgeSeconds ( Auth auth ) {
    return null;
  }

  @Override
  protected String getRestUrl () {
    return elementURL + "&columns=DEFAULT";
  }

  boolean getVerboseDirectory () {
    return verboseDirectory;
  }

  private String makeDots ( int count ) {
    StringBuffer d = new StringBuffer ();
    for ( int i = 0; i < count; i++ ) {
      d.append ( "../" );
    }
    return d.toString ();
  }

  @Override
  protected IAsset requestAsset ( String url ) throws RequestException {
    return xnatfs.getAssetProvider ().getStringAsset ( url, getAuth () );
  }

  @Override
  public void sendContent ( OutputStream out, Range range, Map<String, String> params, String contentType ) throws IOException, NotAuthorizedException {
    XmlWriter w = new XmlWriter ( out );
    w.open ( "html" );
    w.open ( "body" );
    w.begin ( "h1" ).open ().writeText ( this.getName () ).close ();

    com.bradmcevoy.http.XmlWriter.Element header = w.begin ( "h3" ).open ();
    // Write out the hierarchy
    String[] l = this.getAbsolutePath ().split ( "/" );
    if ( l.length > 0 ) {
      l[0] = "(root)";
    }
    w.writeText ( "/" );
    w.begin ( "nbsp" ).close ();
    for ( int i = 0; i < l.length - 1; i++ ) {
      if ( !l[i].equals ( "" ) ) {
        w.begin ( "a" ).writeAtt ( "href", makeDots ( l.length - 1 - i ) ).open ().writeText ( l[i] ).close ();
        w.begin ( "nbsp" ).close ();
        w.writeText ( "/" );
        w.begin ( "nbsp" ).close ();
      }
    }
    if ( l.length > 0 ) {
      w.writeText ( l[l.length - 1] );
    }
    header.close ();
    w.open ( "table" );
    w.open ( "tr" );
    w.open ( "td" );
    w.begin ( "a" ).writeAtt ( "href", ".." ).open ().writeText ( "ParentDirectory" ).close ();
    w.close ( "td" );
    w.close ( "td" );
    for ( Resource rr : getChildren () ) {
      if ( rr == null || !( rr instanceof VirtualResource ) ) {
        logger.warn ( "Child is null or not a VirtualResource!" );
      } else {
        VirtualResource r = (VirtualResource) rr;
        w.open ( "tr" );

        w.open ( "td" );
        String href = r.getName ();
        if ( r instanceof VirtualDirectory ) {
          href = href + "/";
        }
        w.begin ( "a" ).writeAtt ( "href", href ).open ().writeText ( r.getName () ).close ();
        w.close ( "td" );

        w.begin ( "td" ).open ().writeText ( r.getModifiedDate () + "" ).close ();
        w.close ( "tr" );
      }
    }
    w.close ( "table" );
    w.close ( "body" );
    w.close ( "html" );
    w.flush ();
  }

  void setVerboseDirectory ( boolean b ) {
    verboseDirectory = b;
  }
}
